package com.ly.mssp.license.client.registry;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ly.mssp.license.client.http.HttpClient;
import com.ly.mssp.license.client.http.HttpResponse;
import com.ly.mssp.license.client.registry.meta.ManifestMeta;
import com.ly.mssp.license.utils.DateUtils;

import okhttp3.Response;

public class RegistryClient {

	private String baseUrl;

	private String address;

	private int port;
	
	private String userName;
	
	private String password;

	private HttpClient client;

	public RegistryClient(String address, int port, String userName, String password) {
		this.address = address;
		this.port = port;
		this.userName= userName;
		this.password=  password;
		this.baseUrl = "http://" + address + ":" + port + "/v2/";
		this.client = new HttpClient();
	}

	/**
	 * 
	 * 查询镜像信息
	 * 
	 * @param imageName
	 * @return
	 * @throws Exception
	 */
	public List<Image> query(String imageName) throws Exception {
		try {

			List<Image> result = new ArrayList();

			String url = baseUrl + "_catalog?n=100";

			Response response = client.get(url, null);
						
			if (response.code()==401) {
				
				String token=getDockerToken(response);
				
				if (token!=null) {
					
					Map<String, String> headers=new HashMap();
					headers.put("Authorization", "Bearer " + token);
					
					response = client.get(url, headers);
				}else {
					return result;
				}
			}

			if (response.code() == 200) {
				JSONObject json = JSONObject.parseObject(response.body().string());
				if (json != null) {
					List<String> repositories = (List<String>) json.get("repositories");
					if (repositories != null) {
						for (String image : repositories) {
							if (imageName == null || image.contains(imageName)) {
								Image im = new Image();
								if (image.contains("/")) {
									String[] items = image.split("/");
									im.setName(items[1]);
									im.setRepository(items[0]);
								} else {
									im.setName(image);
								}

								result.add(im);

								url = baseUrl + image + "/tags/list";

								try {
									response = client.get(url, null);
									if (response.code()==401) {
										String token=getDockerToken(response);										
										if (token!=null) {
											
											Map<String, String> headers=new HashMap();
											headers.put("Authorization", "Bearer " + token);
											
											response = client.get(url, headers);
										}
									}
									if (response.code() == 200) {
										JSONObject _tags = JSONObject.parseObject(response.body().string());
										List<String> tags = (List<String>) _tags.get("tags");
										if (tags != null && tags.size() > 0) {
											im.setTagCount(tags.size());
										}
									}
								} catch (Exception e) {

								}
							}
						}
					}
				}
			}

			return result;
		} catch (Exception e) {
			throw e;
		}
	}

	public List<ImageTag> queryTags(String imageName, String tagName) throws Exception {
		String url = baseUrl + imageName + "/tags/list";
		List<ImageTag> result = new ArrayList();

		try {

			Response response = client.get(url, null);
			
			if (response.code()==401) {
				
				String token=getDockerToken(response);
				
				if (token!=null) {
					
					Map<String, String> headers=new HashMap();
					headers.put("Authorization", "Bearer " + token);
					
					response = client.get(url, headers);
				}else {
					return result;
				}
			}

			if (response.code() == 200) {
				JSONObject json = JSONObject.parseObject(response.body().string());
				if (json != null) {
					List<String> tags = (List<String>) json.get("tags");
					if (tags != null) {
						for (String _tagName : tags) {
							if (tagName == null || _tagName.contains(tagName)) {
								ImageTag tag = new ImageTag();
								if (imageName.contains("/")) {
									String[] items = imageName.split("/");
									tag.setName(items[1]);
									tag.setRepository(items[0]);
								} else {
									tag.setName(imageName);
								}
								tag.setVersion(_tagName);

								getTagDetail(tag);

								result.add(tag);

							}
						}
					}
				}
			}

			return result;

		} catch (Exception e) {
			throw e;
		}

	}

	/**
	 * 
	 * 查询tag明细
	 * 
	 * @param tag
	 * @return
	 */
	private void getTagDetail(ImageTag tag) {
		long createTime = 0L;

		// http://192.168.30.67:5000/v2/agent/manifests/2.8
		String imageName = tag.getName();
		if (tag.getRepository() != null) {
			imageName = tag.getRepository() + "/" + imageName;
		}

		ManifestClient client = new ManifestClient(this.address, this.port, this.userName, this.password);

		ObjectMapper mapper = new ObjectMapper();
		try {

			String value = client.getManifest(imageName, tag.getVersion());
			
			if (value!=null) {
			
				//String v2=client.getManifest2(imageName, tag.getVersion());
				
				//client.setManifest(imageName, tag.getVersion(), v2);
	
				JsonNode manifests = mapper.readTree(value);
	
				JsonNode history = manifests.get("history");
				if (history != null) {
					Iterator<JsonNode> elements = history.elements();
					while (elements.hasNext()) {
						JsonNode his = elements.next();
						if (his.has("v1Compatibility")) {
							JsonNode v1Compatibility = mapper.readTree(his.get("v1Compatibility").asText());
							String created = v1Compatibility.get("created").asText();
							try {
								long tmpTime = DateUtils.string2timestamp(created, TimeZone.getTimeZone("UTC"));
								if (tmpTime <= createTime) {
									continue;
								}
								createTime = tmpTime;
							} catch (Exception e) {
								e.printStackTrace();
							}
	
							String author = null;
							if (v1Compatibility.has("author")) {
								author = v1Compatibility.get("author").asText();
							}
							String dockerVersion = null;
							if (v1Compatibility.has("docker_version")) {
								dockerVersion = v1Compatibility.get("docker_version").asText();
							}
							tag.setCreateTime(DateUtils.timeStamp2Date(createTime + ""));
	
							// docker pull 192.168.30.67:5000/agent:2.8
							tag.setPullCommand(
									"docker pull " + address + ":" + port + "/" + imageName + ":" + tag.getVersion());
							tag.setAuthor(author);
							tag.setDockerVersion(dockerVersion);
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * 删除镜像信息
	 * 
	 * @param imageName
	 * @param tagName
	 * @return
	 * @throws Exception
	 */
	public boolean delete(String imageName, String tagName) throws Exception {
		
		ManifestClient mc=new ManifestClient(this.address, this.port, this.userName, this.password);
		
		String manifest=mc.getManifest2(imageName, tagName);
		if (manifest!=null) {
			JSONObject json=JSONObject.parseObject(manifest);
			if (json.containsKey("config")) {
				JSONObject config=json.getJSONObject("config");
				String digest=config.getString("digest");
				if (digest!=null) {
					String url=baseUrl + imageName + "/blobs/"+digest;
					Response response = client.delete(url, null);
					if (response.code()!=202 && response.code()!=404) {
						return false;
					}
				}
			}
			
			JSONArray layers=json.getJSONArray("manifests");
			for (int i=0;i<layers.size();i++) {
				JSONObject layer=layers.getJSONObject(i);
				String digest=layer.getString("digest");
				if (digest!=null) {
					String url=baseUrl + imageName + "/blobs/"+digest;
					Response response = client.delete(url, null);
					if (response.code()!=202 && response.code()!=404) {
						return false;
					}
				}
			}
		}

		String url = baseUrl + imageName + "/manifests/" + tagName;

		String digest = client.getDigest(url);

		if (digest!=null) {
			url = baseUrl + imageName + "/manifests/" + digest;
			
			Map<String, String> headers=new HashMap();
			headers.put("Accept", "application/vnd.docker.distribution.manifest.v2+json");
	
			Response response = client.delete(url, headers);
	
			return response.code() == 202;
		}else {
			return true;
		}
	}

	class FileItem {
		ByteArrayOutputStream content = new ByteArrayOutputStream();
		String id;
		long size;
		String json;
		boolean isLast = false;

		public byte[] getBytes() {
			try {
				return this.content.toByteArray();
			} catch (Exception e) {
				return null;
			}
		}
	}

	/**
	 * 
	 * 上传镜像
	 * 
	 * @param tar2
	 * @return
	 */
	public boolean upload(ImageClient imageClient) {
		try {
			String imageName=imageClient.getImageName();
			String tagName=imageClient.getTagName();
			List<String> items=imageClient.getDigests();
			
			int startPos=0, endPos=0;
			
			Map<String, String> headers=new HashMap();
					
			for (int i=items.size()-1;i>=0;i--) {
				String digest=items.get(i);
				String url = baseUrl + imageName + "/blobs/" + digest;
				
				headers.clear();
				Response resp=client.head(url, headers);
				if (resp.code()==200) {
					continue;
				}
				
				String id=imageClient.getId(digest);
				
				headers.clear();				
				url= baseUrl + imageName + "/blobs/uploads/";
				resp=client.post(url, headers);
												
				if (resp.code()==202) {
					url=resp.header("Location");
					
					byte[] content=imageClient.getContent(id);
					if (content!=null) {	
						
						//content=GzipUtils.zip(content);
						
						endPos=content.length+startPos-1;
						headers.put("Content-Range", startPos+"-"+endPos);
						headers.put("Content-Type", "application/octet-stream");
						
						if (url.contains("?")) {
							url=url+"&digest="+digest;
						}else {
							url=url+"?digest="+digest;
						}
						
						System.out.println("Loading layer " + digest);
						
						resp=client.put(url, content, headers);
						
						System.out.println("Loaded layer " + digest);
						
						if (resp.code()!=202  && resp.code()!=201) {
							return false;
						}
						
						startPos=startPos+content.length;
					}					
				}else {
					return false;
				}	
			}	
					
			headers.clear();				
			String url= baseUrl + imageName + "/blobs/uploads/";
			Response resp=client.post(url, headers);
											
			if (resp.code()==202) {
				url=resp.header("Location");
				
				String content=imageClient.getConfig();
				if (content!=null) {	
					byte[] buf=content.getBytes();
					
					String digest=GzipUtils.blobSum(buf);
					//content=GzipUtils.zip(content);					
					headers.put("Content-Type", "application/octet-stream");
					
					if (url.contains("?")) {
						url=url+"&digest="+digest;
					}else {
						url=url+"?digest="+digest;
					}
					
					System.out.println("Loading layer " + digest);
					
					resp=client.put(url, buf, headers);
					
					System.out.println("Loaded layer " + digest);
										
					if (resp.code()!=202  && resp.code()!=201) {
						return false;
					}
				}					
			}else {
				return false;
			}
			
			//complete to upload

			System.out.println("Loading manifest ");
			
			//解析manifest内容
			ManifestMeta meta=imageClient.getManifest();
			ManifestClient mc=new ManifestClient(this.address, this.port, this.userName, this.password);
			
			String manifest=JSONObject.toJSONString(meta);
			
			return mc.setManifest(imageName, tagName, manifest);

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public void download(String imageName, String tagName, ArchiveClient archive) {

		ObjectMapper mapper = new ObjectMapper();

		Map<String, byte[]> result = new HashMap();

		try {
			ManifestClient client = new ManifestClient(this.address, this.port, this.userName, this.password);

			String manifest = client.getManifest(imageName, tagName);

			String manifest2 = client.getManifest2(imageName, tagName);

			if (manifest2 != null) {//
				JSONObject manifests = JSONObject.parseObject(manifest2);
				JSONObject parent_json = new JSONObject();
				JSONObject manifest_json = new JSONObject();
				JSONArray layers = new JSONArray();
				
				JSONArray _histories= null;
				JSONArray levels=null;
				
				JSONObject config=manifests.getJSONObject("config");
				if (config!=null) {
					String digest=config.getString("digest");
					config=null;
					String value=client.getConfig(imageName, digest);
					if (value!=null) {
						config=JSONObject.parseObject(value);
					}
					
					_histories=config.getJSONArray("history");
					levels=config.getJSONObject("rootfs").getJSONArray("diff_ids");
				}

				JSONObject rootfs = new JSONObject();
				rootfs.put("type", "layers");
				
				JSONArray diff_ids = new JSONArray();
				rootfs.put("diff_ids", diff_ids);

				JSONArray histories = new JSONArray();
				parent_json.put("history", histories);

				JSONArray layers_v2 = manifests.getJSONArray("manifests");

				String parent = UUID.randomUUID().toString().replace("-", "")
						+ UUID.randomUUID().toString().replace("-", "");

				Map<String, String> cache = new HashMap();

				String last_id = null;

				String _parent = null;				
			
				// 处理数据层信息
				for (int i = 0;i<layers_v2.size(); i++) {
					JSONObject layer = layers_v2.getJSONObject(i);

					String digest = layer.getString("digest");

					String url = baseUrl + imageName + "/blobs/" + digest;

					Map<String, String> headers=new HashMap();
					headers.put("Content-Type", "application/vnd.docker.image.rootfs.diff.tar.gzip");
					HttpResponse resp = this.client.download(url, headers);

					if (resp != null && resp.code() == 200) {
						byte[] buf = resp.getContent();
						if (buf != null) {
							try {						
								
								JSONObject _v1=null;
								
								if (_histories!=null) {
									int count=-1;
									for (int j=0;j<_histories.size();j++) {
										_v1=_histories.getJSONObject(j);
										if (!_v1.containsKey("empty_layer")) {
											count++;
											if (count==i) {
												break;
											}
										}else {
											_v1=null;
										}
									}
								}
							
								// 处理层信息
								String id = UUID.randomUUID().toString().replace("-", "")
										+ UUID.randomUUID().toString().replace("-", "");
															
								try {
									//解压缩后， 计算blob sum
									buf=GzipUtils.unzip(buf);
									
									if (buf!=null) {
										archive.compress(id + "/layer.tar", buf);
									}
									
									digest=GzipUtils.blobSum(buf);
									
									// 添加层信息
									diff_ids.add(digest);
								} finally {
									// in.close();
								}
								
								// 添加manifest信息
								layers.add(id + "/layer.tar");

								archive.compress(id + "/VERSION", "1.0".getBytes());

								// JSON
								JSONObject history = new JSONObject();
								if (_v1 != null) {
									history.putAll(_v1);
								}
								history.remove("id");
								history.remove("parent");
								
								history.put("id", id);		
								if (_parent!=null) {
									history.put("parent", _parent);
								}

								String val = history.toJSONString();

								archive.compress(id + "/json", val.getBytes());
								
								if (i==layers_v2.size()-1) {
									last_id = id;
								}
								_parent=id;

							} finally {
								// in.close();
							}
						}
					}
				}
				
				

				// write manifest.json
				if (manifest_json != null) {

					manifest_json.put("Config", parent + ".json");

					JSONArray arr = new JSONArray();
					arr.add(this.address + ":" + this.port + "/" + imageName + ":" + tagName);
					manifest_json.put("RepoTags", arr);
					manifest_json.put("Layers", layers);

					JSONArray _manifest = new JSONArray();
					_manifest.add(manifest_json);

					String val = _manifest.toJSONString();

					archive.compress("manifest.json", val.getBytes());

				}

				if (config!=null) {
					parent_json.putAll(config);
				}

				// write parent.json
				String val = parent_json.toJSONString();

				archive.compress(parent + ".json", val.getBytes());

				// write repositories file
				JSONObject json = new JSONObject();
				JSONObject props = new JSONObject();

				if (last_id != null) {
					props.put(tagName, last_id);
				}

				String global = address + ":" + port + "/" + imageName;

				json.put(global, props);
				val = json.toJSONString();

				archive.compress("repositories", val.getBytes());

			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private String getDockerToken(Response response) {
		//String token_url=url+"?account=" + userName + "&client_id=docker&service=" + service + "&scope=" + scope;
		try {
			String authenticate=response.header("Www-Authenticate");
			if (authenticate!=null) {
				String[] items=authenticate.split(",");
				String url=null;
				String service=null;
				String scope=null;
				for (int i=0;i<items.length;i++) {
					String val=items[i];
					if (val.startsWith("Bearer realm=")) {
						url=val.substring(14, val.length()-1);
					}else if (val.startsWith("service=")) {
						service=val.substring(9, val.length()-1);
					}else if (val.startsWith("scope=")) {
						scope=val.substring(7, val.length()-1);
					}
				}	
				
				url=url+"?account="+userName+"&client_id=docker&service="+service+"&scope=" + scope;

				
				Map<String, String> headers=new HashMap();
				String token=userName+":"+password;
				byte[] tmp=Base64.getEncoder().encode(token.getBytes());
				token=new String(tmp);
				headers.put("Authorization", "Basic " + token);
				
				Response resp=this.client.get(url, headers);
				if (resp.code()==200) {
					JSONObject o=JSONObject.parseObject(resp.body().string());
					return o.getString("token");
				}else {
					return null;
				}
				
			}else {
				return null;
			}
		}catch(Exception e) {
			return null;
		}
	}
	
	private String getDockerToken(HttpResponse response) {
		//String token_url=url+"?account=" + userName + "&client_id=docker&service=" + service + "&scope=" + scope;
		try {
			String authenticate=response.header("Www-Authenticate");
			if (authenticate!=null) {
				String[] items=authenticate.split(",");
				String url=null;
				String service=null;
				String scope=null;
				for (int i=0;i<items.length;i++) {
					String val=items[i];
					if (val.startsWith("Bearer realm=")) {
						url=val.substring(14, val.length()-1);
					}else if (val.startsWith("service=")) {
						service=val.substring(9, val.length()-1);
					}else if (val.startsWith("scope=")) {
						scope=val.substring(7, val.length()-1);
					}
				}	
				
				url=url+"?account="+userName+"&client_id=docker&service="+service+"&scope=" + scope;

				
				Map<String, String> headers=new HashMap();
				String token=userName+":"+password;
				byte[] tmp=Base64.getEncoder().encode(token.getBytes());
				token=new String(tmp);
				headers.put("Authorization", "Basic " + token);
				
				Response resp=this.client.get(url, headers);
				if (resp.code()==200) {
					JSONObject o=JSONObject.parseObject(resp.body().string());
					return o.getString("token");
				}else {
					return null;
				}
				
			}else {
				return null;
			}
		}catch(Exception e) {
			return null;
		}
	}
	
	/**
	 * 
	 * 下载远程镜像到本地仓库
	 * 
	 * @param imageName
	 * @param tagName
	 * @param target
	 * @return
	 */
	public boolean clone(String imageName, String tagName, RegistryClient target) {
		ObjectMapper mapper = new ObjectMapper();

		Map<String, byte[]> result = new HashMap();

		try {
			ManifestClient client = new ManifestClient(this.address, this.port, this.userName, this.password);

			String manifest2 = client.getManifest2(imageName, tagName);

			if (manifest2 != null) {//
				JSONObject manifests = JSONObject.parseObject(manifest2);
				JSONObject parent_json = new JSONObject();
				JSONObject manifest_json = new JSONObject();
				JSONArray layers = new JSONArray();
				
				JSONArray _histories= null;
				JSONArray levels=null;
				
				JSONObject config=manifests.getJSONObject("config");
				if (config!=null) {
					String digest=config.getString("digest");
					config=null;
					String value=client.getConfig(imageName, digest);
					if (value!=null) {
						config=JSONObject.parseObject(value);
					}
				}

				JSONArray layers_v2 = manifests.getJSONArray("layers");
			
				// 处理数据层信息
				for (int i = 0;i<layers_v2.size(); i++) {
					
					JSONObject layer = layers_v2.getJSONObject(i);

					String digest = layer.getString("digest");

					String url = baseUrl + imageName + "/blobs/" + digest;

					Map<String, String> headers=new HashMap();
					headers.put("Content-Type", "application/vnd.docker.image.rootfs.diff.tar.gzip");
					HttpResponse resp = this.client.download(url, headers);
					
					if (resp.code()==401) {
						String token=getDockerToken(resp);
						
						if (token!=null) {							
							headers.put("Authorization", "Bearer " + token);
							
							resp = this.client.download(url, headers);
						}else {
							return false;
						}
					}
					
					System.out.println("downloading layer " + digest);

					if (resp != null && resp.code() == 200) {
						byte[] buf = resp.getContent();
						if (buf != null) {							
							url = target.baseUrl + imageName + "/blobs/" + digest;
							
							headers.clear();
							Response _resp=this.client.head(url, headers);
							if (_resp.code()==200) {
								continue;
							}
							
							headers.clear();				
							url= target.baseUrl + imageName + "/blobs/uploads/";
							_resp=this.client.post(url, headers);
															
							if (_resp.code()==202) {
								url=_resp.header("Location");
							
								if (url.contains("?")) {
									url=url+"&digest=" + digest;
								}else {
									url=url+"?digest=" + digest;
								}
								headers.clear();
								headers.put("Content-Type", "application/vnd.docker.image.rootfs.diff.tar.gzip");
								
								_resp=this.client.put(url, buf, headers);
								if (_resp.code()!=201) {
									return false;
								}
							}
						}
					}
				}
				
				//上传 config内容
				if (config!=null) {
					Map<String, String> headers=new HashMap();
					headers.clear();				
					String url= target.baseUrl + imageName + "/blobs/uploads/";
					Response resp=this.client.post(url, headers);
													
					if (resp.code()==202) {
						url=resp.header("Location");
						
						String content=config.toJSONString();
						if (content!=null) {	
							byte[] buf=content.getBytes();
							
							String digest=GzipUtils.blobSum(buf);
							//content=GzipUtils.zip(content);					
							headers.put("Content-Type", "application/octet-stream");
							
							config=manifests.getJSONObject("config");
							config.put("digest", digest);
							
							if (url.contains("?")) {
								url=url+"&digest="+digest;
							}else {
								url=url+"?digest="+digest;
							}
							
							System.out.println("Loading layer " + digest);
							
							resp=this.client.put(url, buf, headers);
							
							System.out.println("Loaded layer " + digest);
												
							if (resp.code()!=202  && resp.code()!=201) {
								return false;
							}
						}					
					}else {
						return false;
					}
				}
				
				System.out.println("downloading manifests ...");
				
				//set manifest
				ManifestClient mc=new ManifestClient(target.address, target.port, target.userName, target.password);
				String value=manifests.toJSONString();
				
				return mc.setManifest(imageName, tagName, value);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return false;
	}
	
	
	@Override
	public void finalize() throws Throwable {
		try {
			this.client.close();
		} catch (Exception e) {

		}
		super.finalize();
	}

}
